package com.fileshare.web.service.impl;

import com.fileshare.web.constant.Status;
import com.fileshare.web.dao.ArticleRepository;
import com.fileshare.web.dto.Bo.ArticleBo;
import com.fileshare.web.dto.Vo.ArticleVo;
import com.fileshare.web.dto.Vo.UserVo;
import com.fileshare.web.entity.*;
import com.fileshare.web.exception.FsRuntimeException;
import com.fileshare.web.service.*;
import com.fileshare.web.utils.SystemUtil;
import com.github.pagehelper.PageInfo;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Service
public class ArticleServiceImpl implements ArticleService {
    public static final int RELATED_AMOUNT = 4;
    @Resource
    private ArticleRepository articleRepository;
    @Resource
    private UserService userService;
    @Resource
    private FileResourceService fileResourceService;
    @Resource
    private ArticleLabelService articleLabelService;
    @Resource
    private LabelService labelService;
    @Resource
    private CategoryService categoryService;

    @Override
    public List<ArticleVo> getAll() {
        List<Article> articles = articleRepository.findAllWithContent();
        return ArticleVo.fromEntities(articles);
    }

    @Override
    public PageInfo<ArticleVo> getArticles(int page, int limit) {
        List<Article> articles = articleRepository.findArticles(page, limit);
        List<ArticleVo> articleVos = constructArticleVos(articles);
        return PageInfo.of(articleVos);
    }

    @Override
    @Transactional
    public ArticleVo getArticle(String articleSeq) throws AccessDeniedException, FsRuntimeException {
        Article article = articleRepository.findArticle(articleSeq);
        if (Objects.nonNull(article)) {
            if (selfViewAccessDeny(article)) {
                throw new AccessDeniedException("Access Denied");
            }
            article.setViews(article.getViews() + 1);
            articleRepository.updateViews(article.getViews(), articleSeq);
            ArticleVo articleVo = ArticleVo.fromEntity(article);
            articleVo.setRelatedArticles(getRelatedArticles(article));
            return articleVo;
        } else {
            throw new FsRuntimeException("Article Not Found");
        }
    }

    @Override
    public PageInfo<ArticleVo> getArticlesByCategoryName(String categoryName) {
        Category category = categoryService.getByName(categoryName);
        List<Article> articles = articleRepository.findByCategorySeq(category.getCategorySeq());
        List<ArticleVo> articleVos = constructArticleVos(articles);
        return PageInfo.of(articleVos);
    }

    @Override
    @Transactional
    public ArticleVo createArticle(HttpServletRequest request, ArticleBo articleBo) {
        Article article = ArticleBo.toEntity(articleBo);
        User user = SystemUtil.getLoginUser();
        Date now = new Date();
        if (Objects.nonNull(user)) {
            String userSeq = user.getUserSeq();
            assembleArticle(articleBo, article, now, userSeq);
            handleArticleFile(articleBo, article);
            articleRepository.save(article);
            handleLabel(articleBo, article, now, userSeq);
        }
        return ArticleVo.fromEntity(article);
    }

    @Override
    public List<Label> getLabels(String articleSeq) {
        return labelService.getArticleLabels(articleSeq);
    }

    public List<ArticleVo> getRelatedArticles(Article article) {
        //获取同类型文章的前四个，或者拿到与这个文章同组的前四个或者组合
        List<ArticleVo> relatedArticles = new ArrayList<>(getRelatedGroupArticles(article));
        if (relatedArticles.size() < RELATED_AMOUNT) {
            relatedArticles.addAll(
                    getRelatedTypeArticles(article, RELATED_AMOUNT - relatedArticles.size())
            );
        }
        return relatedArticles;
    }

    private List<ArticleVo> getRelatedGroupArticles(Article article) {
        return Collections.emptyList();
    }

    private List<ArticleVo> getRelatedTypeArticles(Article article, int remainingAmount) {
        List<Article> articles = articleRepository.findRelatedByCategorySeq(article.getCategorySeq(), remainingAmount);
        return ArticleVo.fromEntities(articles);
    }

    private void assembleArticle(ArticleBo articleBo, Article article, Date now, String userSeq) {
        article.setCreateBy(userSeq);
        article.setUpdateBy(userSeq);
        article.setCreateAt(now);
        article.setUpdateAt(now);
        article.setStatus(getArticleStatus(articleBo));
        article.setCommentCount(0);
        article.setViews(0);
        article.setLikes(0);
        article.setDislikes(0);
        article.setArticleSeq(articleRepository.generateSeq());
        article.setSelfView(articleBo.getSelfView());
        article.setAllowComment(articleBo.getAllowComment());
        article.setAllowLikes(articleBo.getAllowLikes());
        article.setCoverImage(articleBo.getCoverImage());
        article.setSummary(articleBo.getSummary());
        articleBo.setCategorySeq(articleBo.getCategorySeq());
    }

    private void handleArticleFile(ArticleBo articleBo, Article article) {
        if (!CollectionUtils.isEmpty(articleBo.getFileSeqs())) {
            article.setHasResource(true);
            fileResourceService.bindFileToArticle(article, articleBo.getFileSeqs());
        }
    }

    private void handleLabel(ArticleBo articleBo, Article article, Date now, String userSeq) {
        for (String labelName : articleBo.getLabels()) {
            Label label = labelService.findByTypeAndName(article.getCategorySeq(), labelName);
            if (Objects.isNull(label)) {
                label = new Label();
                label.setCategorySeq(articleBo.getCategorySeq());
                String labelSeq = labelService.generateSeq();
                label.setLabelSeq(labelSeq);
                label.setName(labelName);
                label.setCreateAt(now);
                label.setCreateBy(userSeq);
                label.setUpdateAt(now);
                label.setUpdateBy(userSeq);
                labelService.addLabel(label);
            }
            handleArticleLabel(article, now, userSeq, label.getLabelSeq());
        }
    }

    private void handleArticleLabel(Article article, Date now, String userSeq, String labelSeq) {
        ArticleLabel articleLabel = new ArticleLabel();
        articleLabel.setArticleSeq(article.getArticleSeq());
        articleLabel.setLabelSeq(labelSeq);
        articleLabel.setCreateAt(now);
        articleLabel.setCreateBy(userSeq);
        articleLabel.setUpdateAt(now);
        articleLabel.setUpdateBy(userSeq);
        articleLabelService.addArticleLabel(articleLabel);
    }

    private String getArticleStatus(ArticleBo articleBo) {
        return articleBo.getStatus() == null ? Status.Article.PUBLISH : articleBo.getStatus();
    }

    private boolean selfViewAccessDeny(Article article) {
        try {
            User user = SystemUtil.getLoginUser();
            return Boolean.TRUE.equals(article.getSelfView())
                    && (user == null || !user.getUserSeq().equals(article.getCreateBy()));
        } catch (Exception e) {
            return article.getSelfView();
        }
    }

    private List<ArticleVo> constructArticleVos(List<Article> articles) {
        List<ArticleVo> articleVos = new ArrayList<>();
        Map<String, UserVo> userCache = new HashMap<>();
        Map<String, Category> categoryCache = new HashMap<>();
        for (Article article : articles) {
            ArticleVo articleVo = ArticleVo.fromEntity(article);
            UserVo user = userCache.get(article.getCreateBy());
            if (Objects.isNull(user)) {
                user = userService.getUserBySeq(article.getCreateBy());
                userCache.put(article.getCreateBy(), user);
            }
            articleVo.setUser(user);
            Category category = categoryCache.get(article.getCategorySeq());
            if (Objects.isNull(category)) {
                category = categoryService.getBySeq(article.getCategorySeq());
                categoryCache.put(article.getCategorySeq(), category);
            }
            articleVo.setCategory(category);
            articleVos.add(articleVo);
        }
        return articleVos;
    }
}
